{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Test get_llm_friendly_package_docs Function\n",
    "\n",
    "This notebook is designed to test the `get_llm_friendly_package_docs` function from the chat_auto_coder module."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "import os\n",
    "\n",
    "# Add the project root to the Python path\n",
    "project_root = os.path.abspath(os.path.join(os.getcwd(), '..'))\n",
    "sys.path.insert(0, project_root)\n",
    "\n",
    "from src.autocoder.chat_auto_coder import get_llm_friendly_package_docs\n",
    "\n",
    "# Initialize the memory dictionary as it's used in the function\n",
    "memory = {\"libs\": {\"byzer-llm\": {}}}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "['# byzerllm 大模型编程快速指南\\n\\n本文示例 [Notebook](../../notebooks/003_byzerllm_大模型编程快速指南.ipynb)\\n\\n## 安装\\n\\n```bash\\npip install -U byzerllm\\nray start --head\\n```\\n## 启动一个模型代理\\n\\nbyzerllm 支持私有化模型或者SaaS模型的部署。\\n\\n这里以 deepseek 官方API 为例：\\n\\n```bash\\neasy-byzerllm deploy deepseek-chat --token xxxxx --alias deepseek_chat\\n```\\n\\n或者跬基流动API:\\n\\n```bash\\neasy-byzerllm deploy alibaba/Qwen1.5-110B-Chat --token xxxxx --alias qwen110b_chat\\n```\\n\\n将上面的 API KEY 替换成你们自己的。\\n\\n如果你想部署私有化模型或者对接 Ollama 等更多需求，参考 [002_使用byzerllm进行模型部署.md](./002_使用byzerllm进行模型部署.md) 或者 [README.md](../../README.md)。\\n\\n之后，你就可以在代码里使用  deepseek_chat 或者 qwen110b_chat  访问模型了。\\n\\n## hello world\\n\\n来和我们的大模型打个招呼:\\n\\n```python\\nimport byzerllm\\n\\nllm = byzerllm.ByzerLLM.from_default_model(model=\"deepseek_chat\")\\n\\n@byzerllm.prompt(llm=llm)\\ndef hello(q:str) ->str:\\n    \\'\\'\\'\\n    你好, {{ q }}\\n    \\'\\'\\'\\n\\ns = hello(\"你是谁\")    \\nprint(s)\\n\\n## 输出:\\n## \\'你好！我是一个人工智能助手，专门设计来回答问题、提供信息和帮助解决问题。如果你有任何疑问或需要帮助，请随时告诉我。\\'\\n```\\n\\n恭喜，你和大模型成功打了招呼！\\n\\n可以看到，我们通过 `@byzerllm.prompt` 装饰器，将一个方法转换成了一个大模型的调用，然后这个方法的主题是一段文本，文本中\\n使用了 jinja2 模板语法，来获得方法的参数。当正常调用该方法时，实际上就发起了和大模型的交互，并且返回了大模型的结果。\\n\\n在 byzerllm 中，我们把这种方法称之为 prompt 函数。\\n\\n## 查看发送给大模型的prompt\\n\\n很多情况你可能需要调试，查看自己的 prompt 渲染后到底是什么样子的，这个时候你可以通过如下方式\\n获取渲染后的prompt:\\n\\n```python\\nhello.prompt(\"你是谁\")\\n## \\'你好, 你是谁\\'\\n```            \\n\\n## 动态换一个模型\\n\\n前面的 hello 方法在初始化的时候，我们使用了默认的模型 deepseek_chat，如果我们想换一个模型，可以这样做：\\n\\n```python\\nhello.with_llm(llm).run(\"你是谁\")\\n## \\'你好！我是一个人工智能助手，专门设计来回答问题、提供信息和帮助解决问题。如果你有任何疑问或需要帮助，请随时告诉我。\\'\\n```\\n\\n通过 with_llm 你可以设置一个新的 llm 对象，然后调用 run 方法，就可以使用新的模型了。\\n\\n## 超长文本生成\\n\\n我们知道，大模型一次生成的长度其实是有限的，如果你想生成超长文本，你可能需手动的不断获得\\n生成结果，然后把他转化为输入，然后再次生成，这样的方式是比较麻烦的。\\n\\nbyzerllm 提供了更加易用的 API :\\n\\n```python\\nimport byzerllm\\nfrom byzerllm import ByzerLLM\\n\\nllm = ByzerLLM.from_default_model(\"deepseek_chat\")\\n\\n@byzerllm.prompt()\\ndef tell_story() -> str:\\n    \"\"\"\\n    讲一个100字的故事。    \\n    \"\"\"\\n\\n\\ns = (\\n    tell_story.with_llm(llm)\\n    .with_response_markers()\\n    .options({\"llm_config\": {\"max_length\": 10}})\\n    .run()\\n)\\nprint(s)\\n\\n## 从前，森林里住着一只聪明的小狐狸。一天，它发现了一块闪闪发光的宝石。小狐狸决定用这块宝石帮助森林里的动物们。它用宝石的光芒指引迷路的小鹿找到了回家的路，用宝石的温暖治愈了受伤的小鸟。从此，小狐狸成了森林里的英雄，动物们都感激它的善良和智慧。\\n```\\n\\n实际核心部分就是这一行：\\n\\n```python\\ntell_story.with_llm(llm)\\n    .with_response_markers()    \\n    .run()\\n```\\n\\n我们只需要调用 `with_response_markers` 方法，系统就会自动的帮我们生成超长文本。\\n在上面的案例中，我们通过\\n\\n```python\\n.options({\"llm_config\": {\"max_length\": 10}})\\n```\\n\\n认为的限制大模型一次交互最多只能输出10个字符，但是系统依然自动完成了远超过10个字符的文本生成。\\n\\n## 对象输出\\n\\n前面我们的例子都是返回字符串，但是我们也可以返回对象，这样我们就可以更加灵活的处理返回结果。\\n\\n```python\\nimport pydantic \\n\\nclass Story(pydantic.BaseModel):\\n    \\'\\'\\'\\n    故事\\n    \\'\\'\\'\\n\\n    title: str = pydantic.Field(description=\"故事的标题\")\\n    body: str = pydantic.Field(description=\"故事主体\")\\n\\n@byzerllm.prompt()\\ndef tell_story()->Story:\\n    \\'\\'\\'\\n    讲一个100字的故事。    \\n    \\'\\'\\'\\n\\ns = tell_story.with_llm(llm).run()\\nprint(isinstance(s, Story))\\nprint(s.title)\\n\\n## True\\n## 勇敢的小鸟\\n```\\n\\n可以看到，我们很轻松的将输出转化为格式化输出。\\n\\n## 自定义字段抽取\\n\\n前面的结构化输出，其实会消耗更多token,还有一种更加精准的结构化输出方式。\\n比如让大模型生成一个正则表达式，但实际上大模型很难准确只输出一个正则表达式，这个时候我们可以通过自定义抽取函数来获取我们想要的结果。\\n\\n\\n```python\\nfrom loguru import logger\\nimport re\\n\\n@byzerllm.prompt()\\ndef generate_regex_pattern(desc: str) -> str:\\n    \"\"\"\\n    根据下面的描述生成一个正则表达式，要符合python re.compile 库的要求。\\n\\n    {{ desc }}\\n\\n    最后生成的正则表达式要在<REGEX></REGEX>标签对里。\\n    \"\"\"    \\n\\ndef extract_regex_pattern(regex_block: str) -> str:    \\n    pattern = re.search(r\"<REGEX>(.*)</REGEX>\", regex_block, re.DOTALL)\\n    if pattern is None:\\n        logger.warning(\"No regex pattern found in the generated block:\\\\n {regex_block}\")\\n        raise None\\n    return pattern.group(1)\\n\\npattern = \"匹配一个邮箱地址\"\\nv = generate_regex_pattern.with_llm(llm).with_extractor(extract_regex_pattern).run(desc=pattern)\\nprint(v)\\n## ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}$\\n```\\n\\n在上面的例子里，我们根据一句话生成一个正则表达式。我们通过 `with_extractor` 方法，传入了一个自定义的抽取函数，这个函数会在大模型生成结果后，对结果进行处理，然后返回我们想要的结果。\\n\\n我们在 prompt 明确说了，生成的结果要放到 `<REGEX></REGEX>` 标签对里，然后我们通过 extract_regex_pattern 函数，从结果中提取出了我们想要的正则表达式。\\n\\n## 在实例方法中使用大模型\\n\\n```python\\nimport byzerllm\\ndata = {\\n    \\'name\\': \\'Jane Doe\\',\\n    \\'task_count\\': 3,\\n    \\'tasks\\': [\\n        {\\'name\\': \\'Submit report\\', \\'due_date\\': \\'2024-03-10\\'},\\n        {\\'name\\': \\'Finish project\\', \\'due_date\\': \\'2024-03-15\\'},\\n        {\\'name\\': \\'Reply to emails\\', \\'due_date\\': \\'2024-03-08\\'}\\n    ]\\n}\\n\\n\\nclass RAG():\\n    def __init__(self):        \\n        self.llm = byzerllm.ByzerLLM()\\n        self.llm.setup_template(model=\"deepseek_chat\",template=\"auto\")\\n        self.llm.setup_default_model_name(\"deepseek_chat\")        \\n    \\n    @byzerllm.prompt(lambda self:self.llm)\\n    def generate_answer(self,name,task_count,tasks)->str:\\n        \\'\\'\\'\\n        Hello {{ name }},\\n\\n        This is a reminder that you have {{ task_count }} pending tasks:\\n        {% for task in tasks %}\\n        - Task: {{ task.name }} | Due: {{ task.due_date }}\\n        {% endfor %}\\n\\n        Best regards,\\n        Your Reminder System\\n        \\'\\'\\'        \\n\\nt = RAG()\\n\\nresponse = t.generate_answer(**data)\\nprint(response)\\n\\n## 输出:\\n## Hello Jane Doe,\\n##I hope this message finds you well. I wanted to remind you of your 3 pending tasks to ensure you stay on track:\\n## 1. **Submit report** - This task is due on **2024-03-10**. Please ensure that you allocat\\n```\\n\\n这里我们给了个比较复杂的例子，但我们可以看到，给一个实例prompt方法和普通prompt 方法差异不大。\\n唯一的区别是如果你希望在定义的时候就指定大模型，使用一个lambda函数返回实例的 llm 对象即可。\\n\\n```python\\n@byzerllm.prompt(lambda self:self.llm)\\n```\\n\\n你也可以不返回，在调用的时候通过 `with_llm` 方法指定 llm 对象。\\n\\n此外，这个例子也展示了如何通过jinja2模板语法，来处理复杂的结构化数据。\\n\\n## 通过 Python 代码处理复杂入参\\n\\n上面的一个例子中，我们通过 jinja2 模板语法，来处理复杂的结构化数据，但是有时候我们可能需要更加复杂的处理，这个时候我们可以通过 Python 代码来处理。\\n\\n```python\\nimport byzerllm\\n\\ndata = {\\n    \\'name\\': \\'Jane Doe\\',\\n    \\'task_count\\': 3,\\n    \\'tasks\\': [\\n        {\\'name\\': \\'Submit report\\', \\'due_date\\': \\'2024-03-10\\'},\\n        {\\'name\\': \\'Finish project\\', \\'due_date\\': \\'2024-03-15\\'},\\n        {\\'name\\': \\'Reply to emails\\', \\'due_date\\': \\'2024-03-08\\'}\\n    ]\\n}\\n\\n\\nclass RAG():\\n    def __init__(self):        \\n        self.llm = byzerllm.ByzerLLM.from_default_model(model=\"deepseek_chat\")\\n    \\n    @byzerllm.prompt()\\n    def generate_answer(self,name,task_count,tasks)->str:\\n        \\'\\'\\'\\n        Hello {{ name }},\\n\\n        This is a reminder that you have {{ task_count }} pending tasks:\\n            \\n        {{ tasks }}\\n\\n        Best regards,\\n        Your Reminder System\\n        \\'\\'\\'\\n        \\n        tasks_str = \"\\\\n\".join([f\"- Task: {task[\\'name\\']} | Due: { task[\\'due_date\\'] }\" for task in tasks])\\n        return {\"tasks\": tasks_str}\\n\\nt = RAG()\\n\\nresponse = t.generate_answer.with_llm(t.llm).run(**data)\\nprint(response)\\n\\n## Just a gentle nudge to keep you on track with your pending tasks. Here\\'s a quick recap:....\\n```\\n\\n在这个例子里，我们直接把 tasks 在方法体里进行处理，然后作为一个字符串返回，最够构建一个字典，字典的key为 tasks,然后\\n你就可以在 docstring 里使用 `{{ tasks }}` 来引用这个字符串。\\n\\n这样对于很复杂的入参，就不用谢繁琐的 jinja2 模板语法了。\\n\\n## 如何自动实现一个方法\\n\\n比如我定义一个签名，但是我不想自己实现里面的逻辑，让大模型来实现。这个在 byzerllm 中叫 function impl。我们来看看怎么\\n实现:\\n\\n```python\\nimport pydantic\\nclass Time(pydantic.BaseModel):\\n    time: str = pydantic.Field(...,description=\"时间，时间格式为 yyyy-MM-dd\")\\n\\n\\n@llm.impl()\\ndef calculate_current_time()->Time:\\n    \\'\\'\\'\\n    计算当前时间\\n    \\'\\'\\'\\n    pass \\n\\n\\ncalculate_current_time()\\n#output: Time(time=\\'2024-06-14\\')\\n```\\n\\n在这个例子里，我们定义了一个 calculate_current_time 方法，但是我们没有实现里面的逻辑，我们通过 `@llm.impl()` 装饰器，让大模型来实现这个方法。\\n为了避免每次都要“生成”这个方法，导致无法适用，我们提供了缓存，用户可以按如下方式打印速度：\\n\\n```python\\nstart = time.monotonic()\\ncalculate_current_time()\\nprint(f\"first time cost: {time.monotonic()-start}\")\\n\\nstart = time.monotonic()\\ncalculate_current_time()\\nprint(f\"second time cost: {time.monotonic()-start}\")\\n\\n# output:\\n# first time cost: 6.067266260739416\\n# second time cost: 4.347506910562515e-05\\n```\\n可以看到，第一次执行花费了6s,第二次几乎是瞬间完成的，这是因为第一次执行的时候，我们实际上是在生成这个方法，第二次执行的时候，我们是执行已经生成好的代码，所以速度会非常快。你可以显示的调用 `llm.clear_impl_cache()` 清理掉函数缓存。\\n\\n## Stream 模式\\n\\n前面的例子都是一次性生成结果，但是有时候我们可能需要一个流式的输出，这个时候我们可能需要用底层一点的API来完成了：\\n\\n```python\\nimport byzerllm\\n\\nllm = byzerllm.ByzerLLM.from_default_model(model=\"deepseek_chat\")\\n\\nv = llm.stream_chat_oai(model=\"deepseek_chat\",conversations=[{\\n    \"role\":\"user\",\\n    \"content\":\"你好，你是谁\",\\n}],delta_mode=True)\\n\\nfor t in v:\\n    print(t,flush=True)  \\n\\n# 你好\\n# ！\\n# 我\\n# 是一个\\n# 人工智能\\n# 助手\\n# ，\\n# 旨在\\n# 提供\\n# 信息\\n# 、\\n# 解答\\n# 问题....\\n```\\n\\n如果你不想要流式输出，但是想用底层一点的API，你可以使用 `llm.chat_oai` 方法：\\n\\n```python\\nimport byzerllm\\n\\nllm = byzerllm.ByzerLLM.from_default_model(model=\"deepseek_chat\")\\n\\nv = llm.chat_oai(model=\"deepseek_chat\",conversations=[{\\n    \"role\":\"user\",\\n    \"content\":\"你好，你是谁\",\\n}])\\n\\nprint(v[0].output)\\n## 你好！我是一个人工智能助手，旨在提供信息、解答问题和帮助用户解决问题。如果你有任何问题或需要帮助，请随时告诉我。\\n```\\n\\n## Function Calling \\n\\nbyzerllm 可以不依赖模型自身就能提供 function calling 支持，我们来看个例子：\\n\\n\\n```python\\nfrom typing import List,Dict,Any,Annotated\\nimport pydantic \\nimport datetime\\nfrom dateutil.relativedelta import relativedelta\\n\\ndef compute_date_range(count:Annotated[int,\"时间跨度，数值类型\"],\\n                       unit:Annotated[str,\"时间单位，字符串类型\",{\"enum\":[\"day\",\"week\",\"month\",\"year\"]}])->List[str]:\\n    \\'\\'\\'\\n    计算日期范围\\n\\n    Args:\\n        count: 时间跨度，数值类型\\n        unit: 时间单位，字符串类型，可选值为 day,week,month,year\\n    \\'\\'\\'        \\n    now = datetime.datetime.now()\\n    now_str = now.strftime(\"%Y-%m-%d %H:%M:%S\")\\n    if unit == \"day\":\\n        return [(now - relativedelta(days=count)).strftime(\"%Y-%m-%d %H:%M:%S\"),now_str]\\n    elif unit == \"week\":\\n        return [(now - relativedelta(weeks=count)).strftime(\"%Y-%m-%d %H:%M:%S\"),now_str]\\n    elif unit == \"month\":\\n        return [(now - relativedelta(months=count)).strftime(\"%Y-%m-%d %H:%M:%S\"),now_str]\\n    elif unit == \"year\":\\n        return [(now - relativedelta(years=count)).strftime(\"%Y-%m-%d %H:%M:%S\"),now_str]\\n    return [\"\",\"\"]\\n\\ndef compute_now()->str:\\n    \\'\\'\\'\\n    计算当前时间\\n    \\'\\'\\'\\n    return datetime.datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")\\n```\\n\\n我们定义了两个方法，一个是计算日期范围，一个是计算当前时间。\\n\\n现在我么可以来测试下，系统如何根据自然语言决定调用哪个方法：\\n\\n```python\\nt = llm.chat_oai([{\\n    \"content\":\\'\\'\\'计算当前时间\\'\\'\\',\\n    \"role\":\"user\"    \\n}],tools=[compute_date_range,compute_now],execute_tool=True)\\n\\nt[0].values\\n\\n## output: [\\'2024-06-14 15:18:02\\']\\n```\\n\\n我们可以看到，他正确的选择了 compute_now 方法。\\n\\n接着我们再试一个：\\n\\n```python\\nt = llm.chat_oai([{\\n    \"content\":\\'\\'\\'最近三个月趋势\\'\\'\\',\\n    \"role\":\"user\"    \\n}],tools=[compute_date_range,compute_now],execute_tool=True)\\n\\nt[0].values\\n\\n## output: [[\\'2024-03-14 15:19:13\\', \\'2024-06-14 15:19:13\\']]\\n```\\n\\n模型正确的选择了 compute_date_range 方法。\\n\\n## 多模态\\n\\nbyerllm 也能很好的支持多模态的交互，而且统一了多模态大模型的接口，比如你可以用一样的方式使用 openai 或者 claude 的图片转文字能力， 或者一致的方式使用火山，azuer, openai的语音合成接口。\\n\\n### 图生文\\n\\n```python\\nimport byzerllm\\nfrom byzerllm.types import ImagePath\\n\\nvl_llm = byzerllm.ByzerLLM.from_default_model(\"gpt4o_mini_chat\")\\n\\n\\n@byzerllm.prompt()\\ndef what_in_image(image_path: ImagePath) -> str:\\n    \"\"\"\\n    {{ image_path }}\\n    这个图片里有什么？\\n    \"\"\"    \\n\\n\\nv = what_in_image.with_llm(vl_llm).run(\\n    ImagePath(value=\"/Users/allwefantasy/projects/byzer-llm/images/cat1.png\")\\n)\\nv\\n## OUTPUT: 这张图片展示了多只可爱的猫咪，采用了艺术风格的绘画。猫咪们有不同的颜色和花纹，背景是浅棕色，上面还点缀着一些红色的花朵。整体画面给人一种温馨和谐的感觉\\n```\\n\\n可以看到，我们只需要把 prompt 函数的图片地址入参使用 byzerllm.types.ImagePath里进行包装，就可以直接在 prompt 函数体里\\n带上图片。\\n\\n或者你可以这样：\\n\\n```python\\nimport byzerllm\\n\\nvl_llm = byzerllm.ByzerLLM.from_default_model(\"gpt4o_mini_chat\")\\n\\n\\n@byzerllm.prompt()\\ndef what_in_image(image_path: str) -> str:\\n    \"\"\"\\n    {{ image }}\\n    这个图片里有什么？\\n    \"\"\"\\n    return {\"image\": byzerllm.Image.load_image_from_path(image_path)}\\n\\n\\nv = what_in_image.with_llm(vl_llm).run(\\n    \"/Users/allwefantasy/projects/byzer-llm/images/cat1.png\"\\n)\\nv\\n```\\n\\n通过 `image_path` 参数，然后通过 `byzerllm.Image.load_image_from_path` 方法，转化为一个图片对象 image，最后在 prompt 函数体里\\n使用 `{{ image }}` 引用这个图片对象。\\n\\n另外我们也是可以支持配置多张图片的。\\n\\n另外，我们也可以使用基础的 `llm.chat_oai` 方法来实现：\\n\\n```python\\nimport byzerllm\\nimport json\\n\\nvl_llm = byzerllm.ByzerLLM.from_default_model(\"gpt4o_mini_chat\")\\nimage = byzerllm.Image.load_image_from_path(\\n    \"/Users/allwefantasy/projects/byzer-llm/images/cat1.png\"\\n)\\nv = vl_llm.chat_oai(\\n    conversations=[\\n        {\\n            \"role\": \"user\",\\n            \"content\": json.dumps(\\n                [{\"image\": image, \"text\": \"这个图片里有什么？\"}], ensure_ascii=False\\n            ),\\n        }\\n    ]\\n)\\nv[0].output\\n```\\n\\n还可以这么写：\\n\\n```python\\nimport byzerllm\\nimport json\\n\\nvl_llm = byzerllm.ByzerLLM.from_default_model(\"gpt4o_mini_chat\")\\nimage = byzerllm.Image.load_image_from_path(\\n    \"/Users/allwefantasy/projects/byzer-llm/images/cat1.png\"\\n)\\nv = vl_llm.chat_oai(\\n    conversations=[\\n        {\\n            \"role\": \"user\",\\n            \"content\": json.dumps(\\n                [\\n                    {\\n                        \"type\": \"image_url\",\\n                        \"image_url\": {\"url\": image, \"detail\": \"high\"},\\n                    },\\n                    {\"text\": \"这个图片里有什么？\", \"type\": \"text\"},\\n                ],\\n                ensure_ascii=False,\\n            ),\\n        }\\n    ]\\n)\\nv[0].output\\n```\\n\\n### 语音合成\\n\\n这里以 openai 的 tts 为例：\\n\\n```bash\\nbyzerllm deploy --pretrained_model_type saas/openai \\\\\\n--cpus_per_worker 0.001 \\\\\\n--gpus_per_worker 0 \\\\\\n--num_workers 1 \\\\\\n--infer_params saas.api_key=${MODEL_OPENAI_TOKEN} saas.model=tts-1 \\\\\\n--model openai_tts\\n```\\n\\n此外，byzerllm 支持 azure,火山引擎等 tts 语音合成引擎。\\n\\n接着你可以这么用：\\n\\n```python\\nimport byzerllm\\nimport base64\\nimport json\\n\\nllm = byzerllm.ByzerLLM.from_default_model(\"openai_tts\")\\n\\n\\nt = llm.chat_oai(conversations=[{\\n    \"role\":\"user\",\\n    \"content\": json.dumps({\\n        \"input\":\"hello, open_tts\",\\n        \"voice\": \"alloy\",\\n        \"response_format\": \"mp3\"\\n    },ensure_ascii=False)\\n}])\\n\\nwith open(\"voice.mp3\",\"wb\") as f:\\n    f.write(base64.b64decode(t[0].output))\\n```\\n\\ntts 模型生成没有prompt函数可以用，你需要直接使用 chat_oai。\\n\\n\\n### 语音识别\\n\\n这里以 openai 的 whisper-1 为例：\\n\\n```bash\\nbyzerllm deploy --pretrained_model_type saas/openai \\\\\\n--cpus_per_worker 0.001 \\\\\\n--gpus_per_worker 0 \\\\\\n--num_workers 1 \\\\\\n--worker_concurrency 10 \\\\\\n--infer_params saas.model=whisper-1 saas.api_key=${MODEL_OPENAI_TOKEN} \\\\\\n--model speech_to_text\\n```\\n\\n语音识别的使用方式和图生文类似，我们可以直接在 prompt 函数体里带上音频文件。\\n\\n```python\\nimport byzerllm\\nimport json\\nimport base64\\nfrom byzerllm.types import AudioPath\\n\\nllm = byzerllm.ByzerLLM.from_default_model(\"speech_to_text\")\\n\\naudio_file = \"/Users/allwefantasy/videos/output_audio.mp3\"\\n\\n@byzerllm.prompt(llm=llm)\\ndef audio_to_text(audio_file: AudioPath):\\n    \"\"\"\\n    {{ audio_file }}\\n    \"\"\"\\n\\nv = audio_to_text(AudioPath(value=audio_file))\\njson.loads(v)\\n```\\n输出的数据格式略微复杂：\\n\\n```\\n{\\'text\\': \\'In the last chapter, you and I started to step through the internal workings of a transformer. This is one of the key pieces of technology inside large language models, and a lot of other tools in the modern wave of AI.\\',\\n \\'task\\': \\'transcribe\\',\\n \\'language\\': \\'english\\',\\n \\'duration\\': 10.0,\\n \\'segments\\': [{\\'id\\': 0,\\n   \\'seek\\': 0,\\n   \\'start\\': 0.0,\\n   \\'end\\': 4.78000020980835,\\n   \\'text\\': \\' In the last chapter, you and I started to step through the internal workings of a transformer.\\',\\n   \\'tokens\\': [50364,\\n    682,\\n    264,\\n    1036,\\n    7187,\\n    11,\\n    291,\\n    293,\\n    286,\\n    1409,\\n    281,\\n    1823,\\n    807,\\n    264,\\n    6920,\\n    589,\\n    1109,\\n    295,\\n    257,\\n    31782,\\n    13,\\n    50586],\\n   \\'temperature\\': 0.0,\\n   \\'avg_logprob\\': -0.28872039914131165,\\n   \\'compression_ratio\\': 1.4220778942108154,\\n   \\'no_speech_prob\\': 0.016033057123422623},\\n  ....\\n  {\\'id\\': 2,\\n   \\'seek\\': 0,\\n   \\'start\\': 8.579999923706055,\\n   \\'end\\': 9.979999542236328,\\n   \\'text\\': \\' and a lot of other tools in the modern wave of AI.\\',\\n   \\'tokens\\': [50759,\\n    293,\\n    257,\\n    688,\\n    295,\\n    661,\\n    3873,\\n    294,\\n    264,\\n    4363,\\n    5772,\\n    295,\\n    7318,\\n    13,\\n    50867],\\n   \\'temperature\\': 0.0,\\n   \\'avg_logprob\\': -0.28872039914131165,\\n   \\'compression_ratio\\': 1.4220778942108154,\\n   \\'no_speech_prob\\': 0.016033057123422623}],\\n \\'words\\': [{\\'word\\': \\'In\\', \\'start\\': 0.0, \\'end\\': 0.18000000715255737},\\n  {\\'word\\': \\'the\\', \\'start\\': 0.18000000715255737, \\'end\\': 0.23999999463558197},\\n  {\\'word\\': \\'last\\', \\'start\\': 0.23999999463558197, \\'end\\': 0.5400000214576721},\\n  {\\'word\\': \\'chapter\\', \\'start\\': 0.5400000214576721, \\'end\\': 0.800000011920929},\\n  ....\\n  {\\'word\\': \\'AI\\', \\'start\\': 9.920000076293945, \\'end\\': 9.979999542236328}]}\\n```\\n\\n会输出每一句话以及每一个字所在的起始时间和截止时间。你可以根据需要来使用。\\n\\n\\n### 文生图\\n\\n文生图和语音合成类似，首先要启动合适的模型,以openai 的 dall-e-3 为例：\\n\\n```bash\\nbyzerllm deploy --pretrained_model_type saas/openai \\\\\\n--cpus_per_worker 0.001 \\\\\\n--gpus_per_worker 0 \\\\\\n--num_workers 1 \\\\\\n--infer_params saas.api_key=${MODEL_OPENAI_TOKEN} saas.model=dall-e-3 \\\\\\n--model openai_image_gen\\n```\\n\\n启动模型后，只需要记住几个模板参数即可使用，这里直接使用 chat_oai 方法来使用：\\n\\n```python\\n\\nimport byzerllm\\nimport json\\nimport base64\\n\\nllm = byzerllm.ByzerLLM.from_default_model(\"openai_image_gen\")\\nt = llm.chat_oai(conversations=[{\\n    \"role\":\"user\",\\n    \"content\": json.dumps({\\n        \"input\":\"a white siamese cat\",\\n        \"size\": \"1024x1024\",\\n        \"quality\": \"standard\"\\n    },ensure_ascii=False)\\n}])\\n\\nwith open(\"output1.jpg\",\"wb\") as f:\\n    f.write(base64.b64decode(t[0].output))\\n\\n\\nimport matplotlib.pyplot as plt\\n\\nimage_path = \"output1.jpg\"\\nimage = plt.imread(image_path)\\n\\nplt.imshow(image)\\nplt.axis(\\'off\\')\\nplt.show()\\n```\\n\\n## Prompt 函数的流式输出\\n\\nbyzerllm 底层支持流式输出，非 prompt 函数的用法是这样的：\\n\\n```python\\nimport byzerllm\\n\\nllm = byzerllm.ByzerLLM.from_default_model(\"deepseek_chat\")\\n\\nv = llm.stream_chat_oai(conversations=[{\\n    \"role\":\"user\",\\n    \"content\":\"讲一个100字的故事\"\\n}])\\n\\nfor s  in v:\\n    print(s[0], end=\"\")\\n```\\n\\n如果你像用 prompt 函数，可以这么用：\\n\\n\\n```python\\nimport byzerllm\\nimport json\\nimport base64\\nfrom typing import Generator\\n\\nllm = byzerllm.ByzerLLM.from_default_model(\"deepseek_chat\")\\n\\n@byzerllm.prompt()\\ndef tell_story() -> Generator[str, None, None]:\\n    \\'\\'\\'\\n    给我讲一个一百多字的故事\\n    \\'\\'\\'\\n\\nv = tell_story.with_llm(llm).run()    \\nfor i in v:\\n    print(i, end=\"\")\\n```\\n\\n可以看到，和普通的 prompt 函数的区别在于，返回值是一个生成器，然后你可以通过 for 循环来获取结果。\\n\\n## 向量化模型\\n\\nbyzerllm 支持向量化模型,你可以这样启动一个本地的模型：\\n\\n```bash\\n!byzerllm deploy --pretrained_model_type custom/bge \\\\\\n--cpus_per_worker 0.001 \\\\\\n--gpus_per_worker 0 \\\\\\n--worker_concurrency 10 \\\\\\n--model_path /home/winubuntu/.auto-coder/storage/models/AI-ModelScope/bge-large-zh \\\\\\n--infer_backend transformers \\\\\\n--num_workers 1 \\\\\\n--model emb\\n```\\n\\n注意两个参数:\\n\\n1. --infer_backend transformers: 表示使用 transformers 作为推理后端。\\n2. --model_path: 表示模型的路径。\\n\\n也可以启动一个 SaaS 的emb模型,比如 qwen 的 emb 模型：\\n\\n```bash\\nbyzerllm deploy --pretrained_model_type saas/qianwen \\\\\\n--cpus_per_worker 0.001 \\\\\\n--gpus_per_worker 0 \\\\\\n--num_workers 2 \\\\\\n--infer_params saas.api_key=${MODEL_QIANWEN_TOKEN}  saas.model=text-embedding-v2 \\\\\\n--model qianwen_emb\\n```\\n\\n或者 openai 的 emb 模型：\\n\\n```bash\\nbyzerllm deploy --pretrained_model_type saas/openai \\\\\\n--cpus_per_worker 0.001 \\\\\\n--gpus_per_worker 0 \\\\\\n--num_workers 1 \\\\\\n--worker_concurrency 10 \\\\\\n--infer_params saas.api_key=${MODEL_OPENAI_TOKEN} saas.model=text-embedding-3-small \\\\\\n--model gpt_emb\\n```\\nSaaS 模型无需配置 `--infer_backend` 参数。\\n\\n无论是本地模型还是 SaaS 模型，我们都可以这样使用：\\n\\n```python\\nimport byzerllm\\nllm = byzerllm.ByzerLLM.from_default_model(\"deepseek_chat\")\\nllm.setup_default_emb_model_name(\"emb\")\\nllm.emb_query(\"你好\")\\n```\\n\\n如果你配置 byzerllm 中的 Storage 使用，比如你这样启动了存储：\\n\\n```bash\\nbyzerllm storage start --enable_emb\\n```\\n\\n那么需要这么使用：\\n\\n```python\\nfrom byzerllm.apps.byzer_storage.simple_api import ByzerStorage, DataType, FieldOption,SortOption\\nstorage = ByzerStorage(\"byzerai_store\", \"memory\", \"memory\")\\nstorage.emb(\"你好\")\\n```\\n\\n## 一些辅助工具\\n\\n当调用 prompt 函数返回字符串的时候，如果想从里面抽取代码，可以使用如下方式：\\n\\n```python\\nfrom byzerllm.utils.client import code_utils\\ntext_with_markdown = \\'\\'\\'\\n```shell\\nls -l\\n```\\n\\'\\'\\'\\ncode_blocks = code_utils.extract_code(text_with_markdown)\\nfor code_block in code_blocks:\\n    if code_block[0] == \"shell\":\\n        print(code_block[1])\\n##output: ls -l        \\n```\\n\\n\\n## 注意事项\\n\\n1. prompt函数方法体返回只能是dict，实际的返回类型和方法签名可以不一样，但是方法体返回只能是dict。\\n2. 大部分情况prompt函数体为空，如果一定要有方法体，可以返回一个空字典。\\n3. 调用prompt方法的时候，如果在@byzerllm.prompt()里没有指定llm对象，那么需要在调用的时候通过with_llm方法指定llm对象。\\n\\n===========================\\n\\n# Byzer Storage: 最易用的AI存储引擎\\n\\nByzer Storage是一个为AI应用设计的高性能存储引擎,它提供了简单易用的API,支持向量搜索、全文检索以及结构化查询。本文将详细介绍Byzer Storage的使用方法和主要特性。\\n\\n## 0. 安装和启动\\n\\n```bash\\npip install byzerllm\\nbyzerllm storage start\\n```\\n\\nThat\\'s it! Byzer Storage已经安装并启动成功,现在我们可以开始使用它了。默认会启动一个 byzerai_store 的集群。\\n\\n注意，如果你是这样启动的：\\n\\n```bash\\nbyzerllm storage start --enable_emb\\n```\\n\\n那么会自动启动一个 emb 模型，名字就叫 emb， ByzerStorage 会自动使用该模型，无需做任何其他配置。\\n\\n## 1. 初始化\\n\\n创建一个 ByzerStorage 对象，链接 byzerai_store 集群，并且指定数据库和表名（可以不存在）。\\n\\n```python\\nfrom byzerllm.apps.byzer_storage.simple_api import ByzerStorage, DataType, FieldOption, SortOption\\n\\nstorage = ByzerStorage(\"byzerai_store\", \"my_database1\", \"my_table4s\")\\n```\\n\\n\\n## 2. 创建库表（可选）\\n\\nByzer Storage使用Schema来定义数据结构。我们可以使用SchemaBuilder来构建Schema:\\n\\n```python\\n_ = (\\n    storage.schema_builder()\\n    .add_field(\"_id\", DataType.STRING)\\n    .add_field(\"name\", DataType.STRING)\\n    .add_field(\"content\", DataType.STRING, [FieldOption.ANALYZE])\\n    .add_field(\"raw_content\", DataType.STRING, [FieldOption.NO_INDEX])    \\n    .add_array_field(\"summary\", DataType.FLOAT)    \\n    .add_field(\"created_time\", DataType.LONG, [FieldOption.SORT])    \\n    .execute()\\n)\\n```\\n\\n这个Schema定义了以下字段:\\n- `_id`: 字符串类型的主键\\n- `name`: 字符串类型,可用于过滤条件\\n- `content`: 字符串类型,带有ANALYZE选项,用于全文搜索\\n- `raw_content`: 字符串类型,带有NO_INDEX选项,不会被索引\\n- `summary`: 浮点数组类型,用于存储向量\\n- `created_time`: 长整型,带有SORT选项,可用于排序\\n\\n需要注意的是：\\n\\n1. 如果一个字段带有ANALYZE选项,则该字段会被分词,并且可以用于全文搜索，但是就无法返回原始的文本了。所以你需要添加一个新字段专门用来存储原文，比如在我们这个例子里，我们新增了 raw_content 字段，并且显示的指定了 NO_INDEX 选项，这样就不会被索引，也就不会被分词，可以后续被检索后用来获取原始文本。\\n2. 对于需要作为向量检索和存储的字段，需要指定为数组类型，比如我们这个例子里的 summary 字段。\\n3. 如果你需要拿到向量字段的原始文本，那么你也需要添加一个新字段专门用来存储原文，就像我们这个例子里的 raw_content 字段一样。\\n\\n\\n## 3. 写入数据\\n\\n准备数据并使用WriteBuilder写入Storage:\\n\\n```python\\ndata = [\\n    {\"_id\": \"1\", \"name\": \"Hello\", \"content\": \"Hello, world!\", \"raw_content\": \"Hello, world!\", \"summary\": \"hello world\", \"created_time\": 1612137600},\\n    {\"_id\": \"2\", \"name\": \"Byzer\", \"content\": \"Byzer, world!\", \"raw_content\": \"Byzer, world!\", \"summary\": \"byzer\", \"created_time\": 1612137601},\\n    {\"_id\": \"3\", \"name\": \"AI\", \"content\": \"AI, world!\", \"raw_content\": \"AI, world!\", \"summary\": \"AI\", \"created_time\": 16121376002},\\n    {\"_id\": \"4\", \"name\": \"ByzerAI\", \"content\": \"ByzerAI, world!\", \"raw_content\": \"ByzerAI, world!\", \"summary\": \"ByzerAi\", \"created_time\": 16121376003},\\n]\\n\\nstorage.write_builder().add_items(data, vector_fields=[\"summary\"], search_fields=[\"content\"]).execute()\\nstorage.commit()\\n```\\n\\n这里我们使用`add_items`方法批量添加数据,并指定了`summary`为向量字段,`content`为搜索字段。最后调用`commit()`来确保数据被持久化。\\n\\n\\n从上面我们要写入到 byzer storage 的数据我们可以看到如下几个特点：\\n\\n1. 需要包含我们之前定义的 Schema中罗列的所有的字段，同时需要指定哪些是向量字段，哪些是检索字段。\\n2. 向量和检索不能是同一个字段。\\n3. 对于向量，检索字段，我们给到 write_builder 的都是文本，ByzerStorage 会根据 Schema 的定义自动将其转换为向量，检索字段需要的格式。\\n\\n## 4. 查询数据\\n\\nByzer Storage支持多种查询方式,包括向量搜索、全文检索、过滤和排序。\\n\\n### 4.1 向量搜索 + 全文检索\\n\\n```python\\nquery = storage.query_builder()\\nquery.set_vector_query(\"ByzerAI\", fields=[\"summary\"])\\nresults = query.set_search_query(\"Hello\", fields=[\"content\"]).execute()\\nprint(results)\\n```\\n\\n这个查询结合了向量搜索和全文检索,它会在`summary`字段中搜索与\"ByzerAI\"相似的向量,同时在`content`字段中搜索包含\"Hello\"的文档。\\n\\n### 4.2 过滤 + 向量搜索 + 全文检索\\n\\n```python\\nquery = storage.query_builder()\\nquery.and_filter().add_condition(\"name\", \"AI\").build()\\nquery.set_vector_query(\"ByzerAI\", fields=\"summary\")\\nresults = query.set_search_query(\"Hello\", fields=[\"content\"]).execute()\\nprint(results)\\n```\\n\\n这个查询首先过滤`name`字段等于\"AI\"的文档,然后在结果中进行向量搜索和全文检索。\\n\\n### 4.3 过滤 + 排序\\n\\n```python\\nquery = storage.query_builder()\\nquery.and_filter().add_condition(\"name\", \"AI\").build().sort(\"created_time\", SortOption.DESC)\\nresults = query.execute()\\nprint(results)\\n```\\n\\n这个查询过滤`name`字段等于\"AI\"的文档,然后按`created_time`字段降序排序。\\n\\n## 5. 删除数据\\n\\n### 5.1 根据ID删除\\n\\n```python\\nstorage.delete_by_ids([\"3\"])\\n\\nquery = storage.query_builder()\\nquery.and_filter().add_condition(\"name\", \"AI\").build()\\nresults = query.execute()\\nprint(results)\\n```\\n\\n这里我们删除了ID为\"3\"的文档,然后查询验证删除结果。\\n\\n### 5.2 删除整个表\\n\\n```python\\nstorage.drop()\\n```\\n\\n这个操作会删除整个表及其所有数据,请谨慎使用。\\n\\n## 结论\\n\\nByzer Storage提供了一套简洁而强大的API,能够轻松实现向量搜索、全文检索、结构化查询等功能。它的设计非常适合AI应用场景,可以有效地存储和检索各种类型的数据。通过本文的介绍,相信读者已经对Byzer Storage有了基本的了解,并能够开始在自己的项目中使用这个强大的存储引擎。\\n']"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from typing import List, Optional\n",
    "import sys\n",
    "import os\n",
    "\n",
    "memory = {\"libs\": {\"byzer-llm\": {}}}\n",
    "\n",
    "def get_llm_friendly_package_docs(package_name: Optional[str] = None) -> List[str]:\n",
    "    lib_dir = os.path.join(\"..\",\".auto-coder\", \"libs\")\n",
    "    llm_friendly_packages_dir = os.path.join(lib_dir, \"llm_friendly_packages\")\n",
    "    docs = []\n",
    "\n",
    "    if not os.path.exists(llm_friendly_packages_dir):\n",
    "        print(\"llm_friendly_packages directory not found.\")\n",
    "        return docs\n",
    "\n",
    "    libs = list(memory.get(\"libs\", {}).keys())    \n",
    "\n",
    "    for root, dirs, _ in os.walk(llm_friendly_packages_dir):\n",
    "        for dir in dirs:\n",
    "            rel_path = os.path.join(root, dir)\n",
    "            # llm_friendly_packages -> domain -> username -> lib_name。            \n",
    "            rel_path_parts = rel_path.split(os.sep) \n",
    "            if rel_path_parts[-1] in libs:\n",
    "                print(rel_path_parts[-4] == \"llm_friendly_packages\")\n",
    "                                        \n",
    "\n",
    "            if (\n",
    "                len(rel_path_parts) >= 3\n",
    "                and rel_path_parts[-4] == \"llm_friendly_packages\"\n",
    "                and package_name is None or rel_path_parts[-1] == package_name\n",
    "                and rel_path_parts[-1] in libs\n",
    "            ):\n",
    "                package_docs = []\n",
    "                for root, dirs, files in os.walk(rel_path):\n",
    "                    for file in files:\n",
    "                        if file.endswith(\".md\"):\n",
    "                            with open(os.path.join(root, file), \"r\") as f:\n",
    "                                package_docs.append(f.read())\n",
    "                docs.extend(package_docs)\n",
    "                \n",
    "\n",
    "    return docs\n",
    "\n",
    "get_llm_friendly_package_docs()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Analyzing the Results\n",
    "\n",
    "If the function is working correctly, we should see a list of file paths to markdown (.md) files. These files should be located in the `.auto-coder/libs/llm_friendly_packages` directory, within subdirectories that match the libraries specified in the `memory[\"libs\"]` dictionary.\n",
    "\n",
    "If no documents are found, it could mean one of the following:\n",
    "1. The `.auto-coder/libs/llm_friendly_packages` directory doesn't exist.\n",
    "2. There are no markdown files in the correct subdirectories.\n",
    "3. The `memory[\"libs\"]` dictionary is empty or doesn't contain the correct library names.\n",
    "\n",
    "You may need to adjust the `memory[\"libs\"]` dictionary or check the directory structure to ensure everything is set up correctly."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Let's check the directory structure\n",
    "llm_friendly_packages_dir = os.path.join(project_root, '.auto-coder', 'libs', 'llm_friendly_packages')\n",
    "print(f\"Checking directory: {llm_friendly_packages_dir}\")\n",
    "\n",
    "if os.path.exists(llm_friendly_packages_dir):\n",
    "    print(\"Directory exists. Contents:\")\n",
    "    for root, dirs, files in os.walk(llm_friendly_packages_dir):\n",
    "        level = root.replace(llm_friendly_packages_dir, '').count(os.sep)\n",
    "        indent = ' ' * 4 * level\n",
    "        print(f\"{indent}{os.path.basename(root)}/\")\n",
    "        sub_indent = ' ' * 4 * (level + 1)\n",
    "        for f in files:\n",
    "            print(f\"{sub_indent}{f}\")\n",
    "else:\n",
    "    print(\"Directory does not exist.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Based on the results above, you may need to create the necessary directory structure and add some markdown files for testing, or adjust the `memory[\"libs\"]` dictionary to match the existing structure."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "byzerllm",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
